// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include "CaeLocalDataHandleThread.h"
#include "../cae_common/CaeLogConfig.h"
#include "../cae_common/CaeVirtualDeviceConfig.h"
#include "../cae_service/CaeEngineControl.h"
extern "C" {
#include "libavformat/avformat.h"
}

constexpr int MICROSECONDS_PER_SECOND = 1000000;
constexpr int MICROSECONDS_PER_MILLISECOND = 1000;
constexpr int JPEG_STREAM_FPS = 15;
constexpr int YUV_STREAM_FPS = 30;


CaeLocalDataHandleThread::CaeLocalDataHandleThread(uint16_t version, uint16_t devType, uint16_t devId, uint16_t width, uint16_t height, uint32_t format)
{
    m_version = version;
    m_devType = devType;
    m_devId = devId;
    m_width = width;
    m_height = height;
    m_format = format;
}

CaeLocalDataHandleThread::CaeLocalDataHandleThread(uint16_t version, uint16_t devType, uint16_t devId, uint16_t sampleRate, uint32_t format)
{
    m_version = version;
    m_devType = devType;
    m_devId = devId;
    m_sampleRate = sampleRate;
    m_format = format;
}

CaeLocalDataHandleThread::~CaeLocalDataHandleThread()
{
    m_threadStatus = CAE_THREAD_STATUS_EXIT;
    LOGI("CaeLocalDataHandleThread destroy ok.");
}

void CaeLocalDataHandleThread::Run()
{
    char threadName[] = "LocalDataHandle";
    prctl(PR_SET_NAME, threadName);
    CaeLocalDataSendTaskEntry();
    LOGI("CaeLocalDataHandleThread thread exit.");
}

void CaeLocalDataHandleThread::CaeLocalDataSendTaskEntry()
{
    struct MSG_HEADER header{};
    header.version = m_version;
    header.devType = m_devType;
    header.devId = m_devId;

    switch (m_format) {
        case VMI_PCM:
            InjectPcm(header);
            break;
        case VMI_H264:
            InjectH264(header);
            break;
        case VMI_I420:
            InjectYuv(header);
            break;
        case VMI_JPEG:
            InjectJpeg(header);
            break;
    }
    this->m_threadStatus = CAE_THREAD_STATUS_EXIT;
}

long fileSize(FILE* stream)
{
    long length = 0;
    fseek(stream, 0L, SEEK_END);
    length = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    return length;
}

void CaeLocalDataHandleThread::InjectH264(MSG_HEADER header)
{
    header.optType = htons(OPT_CAMERA_VIDEO_FRAME_REQ);
BEGIN:
    AVFormatContext *fmt_ctx = nullptr;
    AVInputFormat * format = av_find_input_format("h264");
    if (avformat_open_input(&fmt_ctx, CaeVirtualDeviceConfig::GetCameraStreamH264FilePath(), format,
                            nullptr)) {
        LOGE("Open input file failed.");
        return;
    }

    if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
        LOGE("Could not find stream information.");
        avformat_close_input(&fmt_ctx);
        return;
    }

    int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (video_stream_idx < 0) {
        LOGE("Could not find video stream.");
        avformat_close_input(&fmt_ctx);
        return;
    }

    AVRational fps = fmt_ctx->streams[video_stream_idx]->avg_frame_rate;
    uint32_t sleepTime = MICROSECONDS_PER_SECOND /  (fps.num / fps.den);

    AVPacket pkt;
    while (this->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        int ret = av_read_frame(fmt_ctx, &pkt);
        if (ret < 0) {
            if (ret == AVERROR_EOF) {
                avformat_close_input(&fmt_ctx);
                goto BEGIN;
            } else {
                break;
            }
        }
        if (pkt.stream_index != video_stream_idx) {
            continue;
        }
        header.len = htonl(sizeof(MSG_HEADER) + pkt.size);
        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, (uint8_t*)&header, sizeof(struct MSG_HEADER));
        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, pkt.data, pkt.size);
        av_packet_unref(&pkt);
        usleep(sleepTime);
    }
    avformat_close_input(&fmt_ctx);
}

void CaeLocalDataHandleThread::InjectYuv(MSG_HEADER header)
{
    FILE *fp = fopen(CaeVirtualDeviceConfig::GetCameraStreamYuvFilePath(), "rb");
    if (fp == nullptr) {
        LOGE("Open file failed : %s.", CaeVirtualDeviceConfig::GetCameraStreamYuvFilePath());
        return;
    }

    header.optType = htons(OPT_CAMERA_VIDEO_FRAME_REQ);
    long oneFrameSize = (m_width * m_height * 3) >> 1;
    header.len = htonl(sizeof(MSG_HEADER) + oneFrameSize);

    auto *buffer = new (std::nothrow) uint8_t[oneFrameSize];
    if (buffer == nullptr) {
        LOGE("Create buffer failed.");
        fclose(fp);
        return;
    }

    while(this->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        uint32_t ret = fread(buffer, oneFrameSize, 1, fp);
        if (ret != 1) {
            fseek(fp, 0, SEEK_SET);
            LOGE("Read file failed.");
            continue;
        }

        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, (uint8_t*)&header, sizeof(struct MSG_HEADER));
        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, buffer, oneFrameSize);
        usleep(MICROSECONDS_PER_SECOND / YUV_STREAM_FPS);
    }
    fclose(fp);
    delete[] buffer;
}

void CaeLocalDataHandleThread::InjectJpeg(MSG_HEADER header)
{
    FILE *fp = fopen(CaeVirtualDeviceConfig::GetCameraStreamJpegFilePath(), "rb");
    if (fp == nullptr) {
        LOGE("Open file failed : %s.", CaeVirtualDeviceConfig::GetCameraStreamJpegFilePath());
        return;
    }

    long bufferSize = fileSize(fp);
    if (bufferSize <= 0) {
        LOGE("Buffer size is invalid.");
        return;
    }

    auto* buffer = (uint8_t*)(new uint8_t[bufferSize]);
    fread(buffer, bufferSize, 1, fp);
    fclose(fp);

    header.optType = htons(OPT_CAMERA_VIDEO_FRAME_REQ);
    header.len = htonl(sizeof(MSG_HEADER) + bufferSize);
    while (this->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, (uint8_t*)&header, sizeof(struct MSG_HEADER));
        CaeEngineControl::GetInstance().VmiDeviceSend(CAMERA, buffer, bufferSize);
        usleep(MICROSECONDS_PER_SECOND / JPEG_STREAM_FPS);
    }
    delete[] buffer;
}

void CaeLocalDataHandleThread::InjectPcm(MSG_HEADER header)
{
    FILE *fp = fopen(CaeVirtualDeviceConfig::GetMicrophoneStreamPcmFilePath(), "rb");
    if (fp == nullptr) {
        LOGE("Open file failed : %s.", CaeVirtualDeviceConfig::GetMicrophoneStreamPcmFilePath());
        return;
    }

    header.optType = htons(OPT_MIC_AUDIO_FRAME);
    const int injectPcmSize = 40 * (m_sampleRate / MICROSECONDS_PER_MILLISECOND);
    int sleepTime = ((injectPcmSize / 2) * MICROSECONDS_PER_MILLISECOND)/ m_sampleRate;
    header.len = htonl(sizeof(header) + injectPcmSize);
    auto *buff = new(std::nothrow) uint8_t[injectPcmSize];
    if(buff == nullptr) {
        LOGE("Create buffer failed.");
        return;
    }

    while (this->m_threadStatus == CAE_THREAD_STATUS_RUNNING) {
        uint32_t ret = fread(buff, injectPcmSize, 1, fp);
        if (ret != 1) {
            fseek(fp, 0, SEEK_SET);
            LOGE("Read file failed.");
            continue;
        }
        CaeEngineControl::GetInstance().VmiDeviceSend(MICROPHONE, (uint8_t*)&header, sizeof(struct MSG_HEADER));
        CaeEngineControl::GetInstance().VmiDeviceSend(MICROPHONE, buff, injectPcmSize);
        usleep(sleepTime * MICROSECONDS_PER_MILLISECOND);
    }
    delete[] buff;
    fclose(fp);
}
